16. Advanced Synchronization

Advanced Synchronization

In this section, you'll learn about some synchronization tools that may come in handy when the synchronized keyword does not fit your use case.

ND079 JPND C2 L05 A18 Advanced Synchronization V2

When to Use the ReentrantLock Class

The synchronized keyword only works with blocks of code — either explicit curly braces after the lock object, or an entire method.

Sometimes, you may want to take ownership of the lock in one method, and release ownership of the lock in another method. Doing this is impossible using only the synchronized keyword.

For these kinds of situations, Java provides the ReentrantLock class.

Here's a code example using ReentrantLock:

public final class VotingApp {
  private final Map<String, Integer> votes = new HashMap<>();|
  private final Lock lock = new ReentrantLock();
  public void castVote(String performer) {
    lock.lock();
    try {
      votes.compute(
        performer, (k, v) -> (v == null) ? 1 : v + 1);
    } finally {
      lock.unlock();
    }
  }
}

The key feature here is that the lock() and unlock() methods can be called anywhere, by any thread that has a reference to the lock object.

You could for example, have a data structure like a HashMap that stores ReentrantLocks as values. Maybe the keys are file names, or some other resource that is shared between threads.

What is a Deadlock?

A deadlock is a kind of concurrent programming error that happens when all threads are stuck waiting for some action. But that action never happens, meaning no threads are ever able to make progress.

One common source of deadlocks is when a thread takes ownership of a lock, but never releases it. This can happen when two threads need the same locks, but take ownership of them in a different order.

What are some reasons why you might want to use the synchronized keyword instead of ReentrantLock?

SOLUTION:
  • If you only need block-scoped locking, `synchronized` is a lot simpler.

Other Kinds of Locks

ReentrantLock is just one kind of lock object.

If you have a moment, take a look in the java.util.concurrent.locks package, which contains other kinds of specialized locks.

It contains other kinds of specialty locks, such as ReadWriteLock. This can be useful when you have some threads that only need to read a shared resource, but other threads need to modify the resource.

Semamphores

The synchronized keyword only allows one thread at a time within a guarded block of code. What do you do if you need to allow more than one thread?

The Semaphore class can help with this scenario.

To create a Semaphore you tell it how many permits it can give out:

Semaphore semaphore = new Semaphore(4);

Usually the number passed to the Semaphore constructor is the number of threads you want to allow in the guarded code, but you could also have a thread taking multiple permits if you want.

Here's how threads obtain and release permits from the semaphore:

try {
  semaphore.acquire();
  // Up to 4 threads can be executing here in parallel!
  // ...
} finally {
  // Give another thread a turn.
  semaphore.release();
}

You might have noticed that if you create a Semaphore with only 1 permit to give out, it behaves the same as a ReentrantLock.

When a lock only allows one thread, you will sometimes here it called a mutex, which is a portmanteau of the term "mutual exclusion".